﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework.Graphics;
using FuncWorks.XNA.XTiled;
using Microsoft.Xna.Framework;
using fess.Graphics;
using fess.Screens;
using Microsoft.Xna.Framework.Input;
using fess.RPG;

namespace fess.Maps
{
  static class VisResources
  {
    public const float GroundDepth = 1, GroundMarksDepth = 0.99f, OnTheGroundDepth = 0.98f, CharDepth = 0.5f, AboveGroundDepth = 0.49f;
  }

  class GenericMap
  {
    enum Faction { Unknown, Enemy, Friend }

    SpriteBatch spriteBatch;

    protected Map map;
    public PassMap passMap;
    public List<IActive> activities = new List<IActive>();
    public FessGame game;

    public bool justMoved;
    protected TimeSpan gameOverTime = TimeSpan.MaxValue;

    public List<Graphics.IDrawable> drawables = new List<Graphics.IDrawable>();
    public List<ActionCharacter> characters = new List<ActionCharacter>();

    public List<MessageWindow> messages = new List<MessageWindow>();
    public List<ActionCharacter> toShow = new List<ActionCharacter>(), enemiesToShow = new List<ActionCharacter>();
    public event Action DropMessages, OnActivate;
    public event Action<ActionCharacter, bool> charAdded, charRemoved;
    public string interaction = null;

    public Rectangle mapView;

    public bool drawObjects = false;
    protected List<MapObject> exits;

    const double fadeDuration = 2;
    double fadeTime = 0;
    Texture2D fade;
    Action fadeAction;

    public GenericMap(FessGame game, Map map)
    {
      this.game = game;
      this.map = map;
      spriteBatch = new SpriteBatch(game.GraphicsDevice);

      exits = map.ObjectLayers[0].MapObjects.Where(obj => obj.Type == "exit").ToList();
      foreach(var exit in exits)
        exit.Polygon.GenerateTexture(game.GraphicsDevice, Color.ForestGreen);

      passMap = new PassMap(map);

      fade = game.Content.Load<Texture2D>("UI/RedLine");
      Map.InitObjectDrawing(game.GraphicsDevice);
    }

    public virtual void Activate()
    {
      if (OnActivate != null)
        OnActivate();

      AddCharacter(game.fess, game.fess.Character.Position, false);
    }

    public Rectangle Bounds { get { return map.Bounds; } }

    public virtual bool Update(GameTime gameTime)
    {
      game.time += gameTime.ElapsedGameTime;
      game.elapsedTime = gameTime.ElapsedGameTime;

      if (game.time > gameOverTime)
      {
        game.screens.Add(new Screens.GameOver(game));
        return false;
      }

      if (game.fess.Dead && gameOverTime > game.time + TimeSpan.FromSeconds(10))
        gameOverTime = game.time + TimeSpan.FromSeconds(10);

      var keys = Keyboard.GetState();

      Vector2 charDelta = Vector2.Zero;
      if (!game.fess.Dead)
      {
        float step = (float)(game.elapsedTime.TotalMilliseconds * game.fess.Speed);
        if (keys.IsKeyDown(Keys.S))
          charDelta.Y += step;
        if (keys.IsKeyDown(Keys.W))
          charDelta.Y -= step;
        if (keys.IsKeyDown(Keys.D))
          charDelta.X += step;
        if (keys.IsKeyDown(Keys.A))
          charDelta.X -= step;

        if (charDelta.Length() != 0)
          charDelta *= step / charDelta.Length();

        if (charDelta.Length() != 0)
        {
          if (!game.fess.Shift(charDelta) && !game.fess.Shift(new Vector2(0, charDelta.Y)))
            game.fess.Shift(new Vector2(charDelta.X, 0));
        }


        if (!game.IsInDialog && exits.Any(x => x.Polygon.RealIntersects(CharRect)))
        {
          interaction = "Переход";
          if (Misc.IsKeyJustPressed(Keys.E))
          {
            if (!ExitEngaged(exits.FirstOrDefault(x => x.Polygon.RealIntersects(CharRect))))
              return false;
          }
        }
        else
        {
          var closest = (from c in characters
                         where (!c.Dead || game.turnUndead) && c != game.fess
                         let talking = c as ITalkingCharacter
                         let isSkel = c is Skeleton
                         where talking != null && !c.Dead || c.Dead && (!isSkel || ((Skeleton)c).calmed)
                         where !isSkel || game.turnUndead
                         let dist = (c.Character.Position - game.fess.Character.Position).Length()
                         orderby dist ascending
                         select new { dist, talking, c }).FirstOrDefault();

          if (!game.IsInDialog && closest != null && closest.dist < 70)
          {
            interaction = closest.c.Name;

            if (closest.c.Dead)
              interaction += " (анимировать)";
            else if (closest.c is Skeleton)
            {
              var skel = (Skeleton)closest.c;
              if (skel.hostile)
                interaction += " (контролировать)";
              else
                interaction += " (упокоить)";
            }
            else
              interaction += " (поговорить)";

            if (Misc.IsKeyJustPressed(Keys.E))
            {
              if (closest.c.Dead)
              {
                var undead = closest.c.MakeUndead();
                if (undead != null)
                {
                  AddCharacter(undead, undead.Character.Position, false, true);
                  undead.AddAI(new MeleeAI(undead, false));
                }
              }
              else
                closest.talking.Talk();
            }
          }
          else
            interaction = null;
        }
      }
      else
        interaction = null;

      justMoved = charDelta != Vector2.Zero;

      for (int i = 0; i < activities.Count; ++i)
        if (!activities[i].Update(game.time))
        {
          activities.RemoveAt(i);
          --i;
        }

      return true;
    }

    protected virtual bool ExitEngaged(MapObject exit)
    {
      return true;
    }

    public Rectangle CharRect
    {
      get
      {
        return new Rectangle((int)(game.fess.Character.Position.X - game.fess.Radius),
      (int)(game.fess.Character.Position.Y - game.fess.Radius), (int)game.fess.Radius * 2, (int)game.fess.Radius * 2);
      }
    }

    public void Clicked(Vector2 where, bool leftClick)
    {
      if (!leftClick)
        game.fess.Fireball(where);
      else
        game.fess.FireArrow(where);
    }

    public virtual void Draw()
    {
      game.GraphicsDevice.Clear(Color.Black);

      spriteBatch.Begin(SpriteSortMode.BackToFront, BlendState.AlphaBlend);
      map.DrawLayer(spriteBatch, 0, mapView, VisResources.GroundDepth);
      map.DrawLayer(spriteBatch, 1, mapView, VisResources.GroundMarksDepth);
      map.DrawLayer(spriteBatch, 2, mapView, VisResources.OnTheGroundDepth);
      for (int i = 3; i < map.TileLayers.Count; ++i)
        map.DrawLayer(spriteBatch, i, mapView, VisResources.AboveGroundDepth - 0.01f * i);

      for (int i = 0; i < drawables.Count; ++i)
        if (!drawables[i].Draw(spriteBatch, mapView, game.time))
        {
          drawables.RemoveAt(i);
          --i;
        }

      if (drawObjects)
        for(int i = 0; i < map.ObjectLayers.Count; ++i)
          map.DrawObjectLayer(spriteBatch, i, mapView, i == 0 ? VisResources.GroundMarksDepth : 0);

      spriteBatch.End();

      if (fadeTime != 0)
      {
        spriteBatch.Begin();
        spriteBatch.Draw(fade, game.GraphicsDevice.Viewport.Bounds, 
          Color.FromNonPremultiplied(0, 0, 0, (int)((1 - Math.Abs(fadeTime - fadeDuration) / fadeDuration) * 255)));
        spriteBatch.End();
        if (fadeAction != null && fadeTime > fadeDuration && fadeTime - game.elapsedTime.TotalSeconds <= fadeDuration)
          fadeAction();

        fadeTime = Math.Max(0, fadeTime - game.elapsedTime.TotalSeconds);
      }
    }

    public void Fade(Action fadeAction)
    {
      this.fadeAction = fadeAction;
      fadeTime = fadeDuration * 2;
    }

    public void MassDamage(Vector2 place, float radius, ActionCharacter attacker, int damage)
    {
      for (int i = 0; i < characters.Count; ++i)
        if ((characters[i].Character.Position - place).Length() <= radius)
          characters[i].DealDamage(damage, attacker, game.time);
    }

    public ActionCharacter GetClosestTarget(Vector2 place, bool enemy)
    {
      var list = enemy ? toShow : enemiesToShow;

      return (from c in list
              where !c.Dead
              orderby (c.Character.Position - place).Length()
              select c).FirstOrDefault();
    }

    public List<ActionCharacter> GetFaction(bool enemy)
    {
      return enemy ? enemiesToShow : toShow;
    }

    public void ChangeFaction(ActionCharacter character, bool enemy)
    {
      var list = enemy ? toShow : enemiesToShow;
      var otherList = enemy ? enemiesToShow : toShow;

      list.Remove(character);
      otherList.Add(character);
      if (charRemoved != null)
        charRemoved(character, !enemy);

      if (charAdded != null)
        charAdded(character, enemy);
    }

    public virtual Vector2 MoveObject(Vector2 from, Vector2 to, float radius, ActionCharacter self = null)
    {
      var pos = passMap.MoveObject(from, to, radius);
      if (pos == from)
        return pos;

      var shift = pos - from;
      var dist = shift.Length();
      pos = from;

      for (float t = 0; t <= dist + 25; t += 25)
      {
        var p = from + shift * Math.Min(dist, t) / dist;

        foreach (var mob in characters)
        {
          var mobPos = mob.Character.Position;
          if (!mob.Dead && mob != self && (p - mobPos).Length() < radius + mob.Radius &&
            (self == null || (p - mobPos).Length() < (from - mobPos).Length()))
            return pos;
        }

        pos = p;
      }

      return pos;
    }

    public void InvokeDropMessages()
    {
      if (DropMessages != null)
        DropMessages();
    }

    public void AddCharacter(ActionCharacter character, Vector2 position, bool enemy, bool display = true)
    {
      if (character.Map != this)
      {
        if (character.Map != null)
          character.Map.RemoveCharacter(character);

        character.Map = this;
        character.Character.Position = position;
        drawables.Add(character.Character);
        characters.Add(character);
        activities.Add(character);

        if (display)
        {
          (enemy ? enemiesToShow : toShow).Add(character);
          if(charAdded != null)
            charAdded(character, enemy);
        }
      }
    }

    Faction GetFaction(ActionCharacter character)
    {
      if (toShow.Contains(character))
        return Faction.Friend;

      if (enemiesToShow.Contains(character))
        return Faction.Enemy;

      return Faction.Unknown;
    }

    public void RemoveCharacter(ActionCharacter character)
    {
      character.Map = null;
      drawables.Remove(character.Character);
      characters.Remove(character);
      activities.Remove(character);
      var faction = GetFaction(character);
      if (charRemoved != null && faction != Faction.Unknown)
        charRemoved(character, faction == Faction.Enemy);
    }
  }
}
